﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Microsoft.Research.DynamicDataDisplay;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using DemoRealtimeData.Service;

namespace DemoRealtimeData.ViewModel
{
    public sealed class MainViewModel : ViewModelBase
    {
        private readonly ISet<string> _selectedDevices;
        private readonly BehaviorSubject<ISet<string>> _selectedMeasKeyChanged;
        private readonly CalculationEngine _engine;
        private readonly CurvePresenter _curvePresenter;
        private bool _isCalculating;

        // ------------------------------------------------ //
        #region "bind-able properties"

        private IEnumerable<KeyValuePair<DataType, string>> _dataTypes;
        public IEnumerable<KeyValuePair<DataType, string>> DataTypes
        {
            get { return _dataTypes; }
            set
            {
                if (_dataTypes == value) return;
                _dataTypes = value;
                RaisePropertyChanged("DataTypes");
            }
        }

        private DataType _currentDataType;
        public DataType CurrentDataType
        {
            get { return _currentDataType; }
            set
            {
                if (_currentDataType == value) return;
                _currentDataType = value;
                RaisePropertyChanged("CurrentDataType");
                UpdateSelectedMeasurements();
            }
        }

        private IEnumerable<DeviceViewModel> _allDevices;
        public IEnumerable<DeviceViewModel> AllDevices
        {
            get { return _allDevices; }
            set
            {
                if (_allDevices == value) return;
                _allDevices = value;
                RaisePropertyChanged("AllDevices");
            }
        }

        private IEnumerable<Measurement> _displayMeasurements;
        public IEnumerable<Measurement> DisplayMeasurements
        {
            get { return _displayMeasurements; }
            set
            {
                if (_displayMeasurements == value) return;
                _displayMeasurements = value;
                RaisePropertyChanged("DisplayMeasurements");
            }
        }

        public RelayCommand StartCalculateCmd { get; private set; }

        #endregion

        // ------------------------------------------------ //
        #region "constructor"

        public MainViewModel(ChartPlotter plotter)
        {
            _selectedMeasKeyChanged = new BehaviorSubject<ISet<string>>(null);

            _selectedDevices = new HashSet<string>();
            Messenger.Default.Register<DevSelectedChangedMsg>(this, msg =>
            {
                if (msg.IsSelected)
                {
                    _selectedDevices.Add(msg.Device);
                }
                else
                {
                    _selectedDevices.Remove(msg.Device);
                }
                UpdateSelectedMeasurements();
            });

            DataTypes = Common.DataType2Description;
            CurrentDataType = DataType.VM;

            var db = Common.GetDatabase();

            _curvePresenter = new CurvePresenter(db.Keys, plotter);
            _selectedMeasKeyChanged.Subscribe(_curvePresenter.OnSelectionChanged);

            AllDevices = (from key in db.Keys
                          let devname = key.Parse().Item1
                          group devname by devname
                              into grp
                              select new DeviceViewModel(grp.Key)).ToArray();

            _engine = new CalculationEngine();

            StartCalculateCmd = new RelayCommand(
                StartCalculation,
                () => !_isCalculating);
            _isCalculating = false;
        }

        #endregion

        // ------------------------------------------------ //
        #region "private helpers"

        private void UpdateSelectedMeasurements()
        {
            ISet<string> selectedMeasKeys =
                _selectedDevices.Aggregate(new HashSet<string>(),
                (set, dev) =>
                {
                    set.Add(Common.MakeKey(dev, _currentDataType));
                    return set;
                });
            _selectedMeasKeyChanged.OnNext(selectedMeasKeys);
        }

        private void StartCalculation()
        {
            _isCalculating = true;

            IConnectableObservable<Measurement[]> measStream = _engine.Start().Publish();

            // ------------------ refresh curves
            measStream
                .ObserveOnDispatcher()
                .Subscribe(_curvePresenter.OnNewMeasurements, () => { _isCalculating = false; });

            // ------------------ update the current values
            // for this combined stream, we don't react to its Completed event
            // because it will never completed (since one of the combinator, the BehaviorSubject
            // never completes)
            measStream.CombineLatest(
                _selectedMeasKeyChanged, 
                (allMeasurements,selectedKeys)=>allMeasurements.Where(m => selectedKeys.Contains(m.Key)).ToArray())
                .ObserveOnDispatcher()
                .Subscribe(measurements => { DisplayMeasurements = measurements; });

            measStream.Connect();
        }

        #endregion
    }
}
